AI 지원 프로그래밍을 위한 새 실천법
- 2025-03-24
AI 지원 프로그래밍 환경에 맞는 새 프로그래밍 실천법에 대한 정리되지 않은 고민들.
언제 무엇을 AI에게 시킬지 판단하기
인간-AI 협업이 어떤 상황에서 더 나은 결과를 보이는지에 대한 메타 연구(When combinations of humans and AI are useful - A systematic review and meta-analysis)에 따르면…
- 인간이 AI보다 잘하는 일에 AI를 붙여주면 결과가 더 좋았고
- AI가 인간보다 잘하는 일에 인간을 붙여주면 결과가 더 나빴다.
저자들은 인간이 AI보다 전반적으로 잘하는 경우에라야 AI의 제안을 수용할지 여부도 더 잘 판단할 수 있기 때문일 것으로 추측한다. 그러니까 인간이 AI보다 못하는 상황에선 AI를 의심할 타이밍에 의심 안하거나(overreliance), AI의 제안을 수용할 타이밍에 고집을 부리거나(underreliance) 한다는 얘기.
언제 어떤 일을 AI에게 시킬지, AI가 제안한 결과를 어떤 상황에서 얼마나 신뢰할지 등을 구분하는 능력이 중요하겠다. 평소에 어떤 실천법을 쓰면 이런 능력을 개발할 수 있을지에 대해서도 고민이 필요하겠다.
미래(6개월 뒤일지, 10년 뒤일지 모름)에는 AI가 모든 걸 다 잘하게 되겠지만 지금 당장은 아니기 때문에 당분간은 고민과 대비가 필요하다.
얼마나 큼직하게 시킬지 판단하기
TDD 주기의 보폭을 의식적으로 제어하는 것과 유사하게, AI에게 얼마나 큰 단위로 일을 줄지 의식적으로 정하는 게 중요해 보인다.
그러려면 아주 좁은 보폭(전통적인 방식으로 코딩하며 고스트 텍스트 형태의 제안을 수용하며 진행)과 아주 넓은 보폭(바이브 코딩)에 모두 익숙해질 필요가 있다. 그래야 원하는 순간에 원하는 보폭을 선택할 수 있으니까.
어떤 모델에게 어떤 방식으로 지시하면 어느 정도의 일을 해낼 수 있는지에 대한 감을 잡아야 할텐데, 업무 중 이 감을 높이기 위한 구체적인 실천법에 대해서는 고민이 필요. 어쩌면 조만간 AI가 알아서 라우팅을 잘 해줄 수도 있겠다. 지금도 이미 Cursor 등에는 모델 자동 선택 옵션이 있는데 얼마나 잘 되는지는 의문.
참고:
양질의 피드백 루프 만들어주기
단위 테스트, 정적 타입 검사 등으로 빠르고 정확하고 풍성한 피드백 환경을 갖추는 건 원래 중요했다. AI 에이전트를 써서 코딩하는 상황에서도 마찬가지로 중요하다. AI가 스스로의 실수를 빠르게 점검하고 수정할 수 있도록 도와주어야 한다. 실제로 에이전트 모드를 사용해보면 단위 테스트와 정적 타입 검사가 갖춰졌는지 여부가 대단히 중요하다는 걸 알 수 있다.
기존의 좋은 실천법들(격리된 테스트, 빠른 테스트, 높은 커버리지 등)이 더 중요해졌다.
Verification과 Validation의 구분
Verification은 목표로 한 일이 잘 되었는지를 확인하는 걸 말하고, Validation은 애초에 목표가 적절한지를 확인하는 걸 말한다. 예를 들어 “한국인 평균 소득을 구하기 위해 산술평균을 써야지”라고 정한 뒤 “산술평균을 올바르게 구했는지?” 확인하는 건 “Verification”이다. 한편, “평균 소득을 구할 때 산술평균을 쓰는 게 맞는지?”를 확인하는 건 “Validation”이다.
Validation 없이 Verification만 하면 이상한 일을 제대로 수행하게 된다. AI의 환각을 주의하라거나 하는 조언은 대부분 “Verification”에 초점이 맞춰져 있다. 하지만 애초에 AI에게 잘못된 지시를 내린 뒤에 그 지시를 잘 수행했는지 확인하는 건 의미가 없다.
이것도 역시 AI와 무관하게 원래 중요했지만, Verification 단계가 점차 자동화됨에 따라 Validation의 중요성이 더 커진 것 같다.
환각에 대응하기
소프트웨어 결함의 비용은 결함 발견까지의 시간에 비례한다는 점을 고려하면(DCI) 환각을 얼마나 빠르게 발견하는지가 매우 중요한 문제다.
없어도 되는 코드가 만들어지고 이걸 계속 짊어지고 가는 상황도 문제다. 코드는 자산이기도 하지만 다른 한편으로는 부채이므로 기능이 동일하다면 코드가 짧을수록 좋다. 하지만 맥락을 모르는 AI는 불필요한 코드를 생성하는 경향이 있는데, 이게 그 자체로 버그는 아니므로 그냥 쌓아두는 경향이 있다. 하지만 불필요한 코드는 이후에 인간과 AI가 코드를 읽을 때 매번 비용을 발생시키고 착오를 유발하며 리팩토링 부담을 증가시킬 수 있다.
역시나 단위 테스트와 정적 타입 검사 등이 중요.
코드 쓰기와 읽기의 비중 변화
내가 코드를 작성하는 비중이 점점 줄고, AI가 제안한 코드를 검토하는 비중은 점점 늘고 있다. AI가 작성하는 코드가 적어도 절반 이상인 것 같다. 코드를 잘 쓰는 능력보다 코드를 잘 읽는 능력이 훨씬 더 중요해질 것 같다. 코드 읽기에 대한 훈련이 필요하겠다.
AI가 제안하는 코드에는 (아직까지는) 언제나 오류 가능성이 있는데, 인간 프로그래머가 범하는 오류와 조금 다른 종류의 오류를 범하곤 하는 게 문제인 것 같다. 단위 테스트, 강력한 타입 시스템, 프로그램 증명 등의 장점을 잘 취사선택하는 어떤 실천법이 필요하다고 생각한다.
주석과 이름의 역할 변화
깃헙 코파일럿이나 Cursor 등이 나오기 전까지는 주석은 사람에게 의미가 있었을 뿐 컴퓨터에게는 아무 의미가 없거나 아주 제한적으로만 의미가 있었다(JavaDoc이나 파이썬 docstring 등). 각종 이름(변수 이름, 함수 이름, 클래스 이름 등)도 마찬가지.
예를 들어 아래 두 코드는 컴퓨터가 보기에 동일하다(-equivalence).
const blah = a => Math.PI * a * a
const area = r => Math.PI * r * r
하지만 LLM 기반 코딩 지원 도구가 나온 이후로는, IDE가 주석과 이름을 알아먹기 시작했다. 이에 따라 AI 지원 프로그래밍 환경에서는 프로그래머가
const blah = a =>
를 입력하면 편집기가 유익한 제안을 못해주지만const area = r =>
를 입력하면 높은 확률로Math.PI * r * r
를 제안해준다.
주석도 마찬가지다. 주석을 잘 적으면 맥락에 맞는 코드를 더 잘 제안해준다.
좋은 이름을 지어주고 필요한 주석을 적소에 잘 적어주는 건 원래 해야할 일이기 때문에 결과만 보면 별 차이가 없다. 하지만 결과를 만드는 과정에는 제법 큰 차이가 생긴 것 같다. 예:
- TDD를 할 때, 실패하는 테스트를 새로 추가한 뒤에는 최대한 빠르게(대체로 30초 이내에) 녹색 막대를 보는 게 중요하다(참고: TDD 주기). 그래서 이 단계에서는 변수 이름을
blah
,foo
,a
,b
등으로 대충 지은 다음에 테스트가 통과하면 점진적으로 리팩토링을 하곤 한다. 하지만 이름을 대충 지으면 AI의 코드 지원을 제대로 받을 수 없다. - TDD를 할 때에는 테스트를 통과할 수 있는 최소한의 구현을 일단 해놓고서(Fake it) 점진적으로 구현을 유도해가게 되는데, AI는 종종 한 방에 제대로 된 구현을 제안한다. 그러고 나면 다음 테스트를 추가해도 테스트가 깨지지 않기 때문에(왜냐하면 이미 제대로 된 구현이 생겼으니까) 피드백 고리가 끊어진다. 결과적으로 테스트 커버리지가 낮아질 소지가 있다.
한편, 기존 실천과 상충하지 않는 변화도 있다. 몇몇 변화는 기존의 좋은 실천법들이 더 유익해지게 만든다. 예:
- 코딩을 하기에 앞서 이번 코딩 세션에서 하려고 계획한 일을 주석에 의사코드 또는 TODO 목록 형태로 적는 일이 잦아졌다. 이는 짝 프로그래밍 세션을 시작할 때 짝이랑 화이트보드나 종이에 낙서를 해가며 짧은 디자인 세션을 여는 것과 유사하며, 장려할만한 실천법이다. 이걸 혼자 코딩하는 상황에서도 더 의식적으로 하게 되었으니 이는 좋은 일이다.
- AI가 주석에 적힌 의사 코드(pseudo code)를 알아듣는 덕에 점진적 개선(stepwise refinement) 기법이 훨씬 유익해졌고, 이에 따라 더 자주 활용하고 있다.
- 문학적 프로그래밍도 마찬가지. Google Colab 같은 환경에서는 아직 충분히 잘 작동하지 않지만 VSCode에서 Jupyter Notebook을 쓰는 경우 AI 지원이 제법 잘 작동한다.
어떤 변화는 좋은지 나쁜지 아직 잘 모르겠다. 예:
- 함수 내부의 주석은 대체로 악취이지만 함수나 모듈 선언부(특히 “published” 인터페이스인 경우)의 주석은 꼼꼼하게 잘 적어주는 게 좋다. 그런데 리팩토링을 충분히 빈번하게 하려면 선언부 주석은 커밋 직전에 적는 게 효율적이다. 그런데 이제는 주석 작성을 AI가 잘 도와주고 리팩토링한 코드에 맞춰 주석을 고치는 일도 AI가 잘 도와주다보니, 주석을 미리 쓰는 게 큰 낭비가 아니게 됐다.
소프트웨어 설계
마틴 파울러의 리팩토링에는 이런 문구가 나온다:
After all, the compiler doesn’t care whether the code is ugly or clean. But when I change the system, there is a human involved, and humans do care. —p4
코드의 구조가 깔끔하건 아니건 컴퓨터(컴파일러)는 신경쓰지 않지만 인간에겐 중요하다는 말이다. 하지만 이제 컴퓨터에게도 깔끔한 코드가 중요해졌다. 의도가 더 잘 드러나고 설계가 더 좋은 코드일수록 AI가 더 잘 작동할 가능성이 커졌기 때문이다.
이런 변화로 인해, 설계를 언제 하는 게 좋을지에 대해서도 더 고민이 필요해졌다.
코드 작성 순서의 변화
코드의 어떤 부분을 먼저 작성하면 내 의도를 더 잘 전달할 수 있을지를 더 작은 스케일, 더 좁은 간격으로 고민할 필요가 있다. 깃헙 코파일럿 대신 Cursor를 쓰고 있다면, 심지어 편집을 마친 후 커서를 어디에 가져다 놓을지도 중요해진다. (이건 아직 확실치 않은데) 클립보드에 뭘 복사했는지도 참고하는 것으로 보인다.
더 자주 커밋하기
어차피 커밋 메시지도 LLM이 써주니까 더 자주 커밋하는 게 좋을 것 같다. AI 에이전트가 코드를 망쳐놔도 언제나 돌아갈 수 있게.
너무 잦은 커밋은 적절히 git squash 하면 되니까 문제 없다.
생산성 향상 vs. 역량 향상
단기적 생산성 향상과 장기적 역량 향상은 종종 서로 충돌함에도 불구하고(Exploration-exploitation dilemma) 상당수의 생성 AI 논의는 전자에 집중하거나 양자를 대충 뒤섞는다.
국내 논의들도 마찬가지인데, 예를 들어 무신사의 GitHub Copilot은 정말로 우리의 생산성을 높였을까는 주로 생산성에 집중한다. SK플래닛의 GitHub Copilot 활용기도 주로 생산성에 집중한다. 다만 “5. 유의 사항”에서 역량 이야기가 짧게 언급되기는 한다.
생산성 관점이 “일을 더 빨리 할 수 있나?”를 묻는다면 역량 관점은 “내 역량이 더 나아지나?”를 묻는다. 나는 후자에 좀 더 관심이 있다. (전자를 극대화하는 접근으로는 Vibe coding을 꼽을 수 있겠다)